/*
 * @Author: czy
 * @Date: 2021-05-30 11:56:19
 * @LastEditTime: 2021-06-02 09:32:57
 */
#include "string.h"

#include "api/system.h"

extern void memused(unsigned long size);
extern void memfree(unsigned long size);

/**
 * @description: Gets the required memory size.
 * @param perSize   Size of each block.
 * @param n         Number of memory blocks.
 * @return Total size.
 */
unsigned long eFPartitionTotalSize(unsigned long perSize, unsigned long n)
{
    perSize = ((((perSize > sizeof(Block) ? perSize : sizeof(Block)) - 1) / ALIGN) + 1) * ALIGN;
    return perSize * n + sizeof(Partition);
}

/**
 * @description: Equal size partition initialization.
 * @param start     Start address.
 * @param perSize   Size of each block.
 * @param n         Number of memory blocks.
 * @return Handle if success, else 0.
 */
unsigned long eFPartitionInit(unsigned long start, unsigned long perSize, unsigned long n)
{
    unsigned long pMemStart = getMemStart();
    unsigned long pMemSize = getMemSize();
    if (eFPartitionTotalSize(perSize, n) > pMemSize)
    {
        return 0;
    }
    perSize = ((((perSize > sizeof(Block) ? perSize : sizeof(Block)) - 1) / ALIGN) + 1) * ALIGN;
    start = start > pMemStart ? start : pMemStart;

    char *pointer = (char *)start;
    Partition partition;
    partition.head = (unsigned long)start + sizeof(Partition);
    partition.meta = perSize;
    memcpy((void *)start, &partition, sizeof(Partition));
    pointer += sizeof(Partition);

    Block block;
    for (int i = 0; i < n; i++)
    {
        block.size = perSize;
        block.previous = i == 0 ? 0 : (unsigned long)(pointer - perSize);
        block.next = i == n - 1 ? 0 : (unsigned long)(pointer + perSize);
        memcpy(pointer, &block, sizeof(Block));
        pointer += perSize;
    }
    return start;
}

/**
 * @description: Alloc a block of memory.
 * @param EFPHandler    EFP handle.
 * @return Handle if success, else 0.
 */
unsigned long eFPartitionAlloc(unsigned long EFPHandler)
{
    Partition *partition = (Partition *)EFPHandler;
    if (partition->head == 0)
    {
        return 0;
    }
    Block *block = (Block *)(partition->head);
    unsigned long address = (unsigned long)block;
    Block *nextBlock = (Block *)(block->next);
    if ((unsigned long)nextBlock != 0)
    {
        nextBlock->previous = 0;
        partition->head = (unsigned long)nextBlock;
    }
    else
    {
        partition->head = 0;
    }
    return address;
}

/**
 * @description: Free a block of memory.
 * @param EFPHandler    EFP handle.
 * @param mbStart       Start address of memory block.
 * @return 1
 */
unsigned long eFPartitionFree(unsigned long EFPHandler, unsigned long mbStart)
{
    Partition *partition = (Partition *)EFPHandler;
    Block newBlock;
    newBlock.previous = 0;
    newBlock.next = partition->head;
    newBlock.size = partition->meta;
    Block *tempBlock = (Block *)(partition->head);
    if ((unsigned long)tempBlock != 0)
    {
        tempBlock->previous = (unsigned long)mbStart;
    }
    memcpy((void *)mbStart, &newBlock, sizeof(Block));
    partition->head = (unsigned long)mbStart;
    return 1;
}

/**
 * @description: Dynamic partition initialization.
 * @param start Start address.
 * @param size  Total size.
 * @return Handle if success, else 0.
 */
unsigned long dPartitionInit(unsigned long start, unsigned long size)
{
    unsigned long pMemStart = getMemStart();
    unsigned long pMemSize = getMemSize();
    start = start > pMemStart ? start : pMemStart;
    if (size > pMemSize)
    {
        return 0;
    }
    size = size > sizeof(Block) ? size : sizeof(Block);
    size = (((size + sizeof(Partition) - 1) / ALIGN) + 1) * ALIGN;

    char *pointer = (char *)start;
    Partition partition;
    partition.head = (unsigned long)start + sizeof(Partition);
    partition.meta = size;
    memcpy((void *)start, &partition, sizeof(Partition));
    pointer += sizeof(Partition);

    Block block;
    block.size = size;
    block.previous = 0;
    block.next = 0;
    memcpy(pointer, &block, sizeof(Block));
    return start;
}

/**
 * @description: Alloc a block of memory. (First-fit version)
 * @param dpHandler    DP handle.
 * @param size  Size of memory block.
 * @return Handle if success, else 0.
 */
static unsigned long dPartitionAllocFirstFit(unsigned long dpHandler, unsigned long size)
{
    Partition *partition = (Partition *)dpHandler;
    Block *block = (Block *)(partition->head);
    int flag = 0;
    size = (((size + 4 - 1) / ALIGN) + 1) * ALIGN;
    while (block != 0)
    {
        if (block->size >= size)
        {
            flag = 1;
            break;
        }
        block = (Block *)block->next;
    }
    if (flag == 0)
    {
        return 0;
    }
    memused(size);
    unsigned long address = (unsigned long)block + size;
    Block newBlock;
    newBlock.size = block->size - size;
    newBlock.next = block->next;
    newBlock.previous = block->previous;
    memcpy((void *)address, &newBlock, sizeof(Block));
    Block *tempBlock;
    if (block->previous != 0)
    {
        tempBlock = (Block *)(block->previous);
        tempBlock->next = address;
    }
    else
    {
        partition->head = address;
    }
    if (block->next != 0)
    {
        tempBlock = (Block *)(block->next);
        tempBlock->previous = address;
    }
    unsigned long *blocksize = (unsigned long *)block;
    *blocksize = size;
    return (unsigned long)block + 4;
}

/**
 * @description: Free a block of memory. (First-fit version)
 * @param dpHandler DP handle.
 * @param start     Start address of memory block.
 * @return 1
 */
static unsigned long dPartitionFreeFirstFit(unsigned long dpHandler, unsigned long start)
{
    Partition *partition = (Partition *)dpHandler;
    if (start > (unsigned long)partition + partition->meta || start < (unsigned long)partition)
    {
        return 0;
    }
    Block *block = (Block *)(partition->head);
    Block *previousBlock, *nextBlock;
    Block newBlock;
    int flag = 0;
    unsigned long address = start - 4;
    newBlock.size = *((unsigned long *)address);
    newBlock.size = newBlock.size > ALIGN ? newBlock.size : ALIGN;
    memfree(newBlock.size);
    while (block != 0)
    {
        if ((unsigned long)block > start)
        {
            flag = 1;
            break;
        }
        block = (Block *)block->next;
    }
    if (flag == 1)
    {
        newBlock.previous = block->previous;
        newBlock.next = (unsigned long)block;
        previousBlock = (Block *)block->previous;
        if ((unsigned long)previousBlock != 0)
        {
            if ((unsigned long)previousBlock + previousBlock->size == address)
            {
                // Merge with previous memory block.
                address = (unsigned long)previousBlock;
                newBlock.size += previousBlock->size;
                newBlock.next = previousBlock->next;
                newBlock.previous = previousBlock->previous;
            }
            else
            {
                previousBlock->next = address;
            }
        }
        else
        {
            partition->head = address;
        }
        if (address + newBlock.size == (unsigned long)block)
        {
            // Merge with next memory block.
            newBlock.size += block->size;
            newBlock.next = block->next;
            nextBlock = (Block *)(block->next);
            if ((unsigned long)nextBlock != 0)
            {
                nextBlock->previous = block->previous;
            }
        }
        else
        {
            block->previous = address;
        }
    }
    else
    {
        if ((unsigned long)block + block->size == address)
        {
            address = (unsigned long)block;
            newBlock.size += block->size;
            newBlock.next = block->next;
            newBlock.previous = block->previous;
        }
        else
        {
            newBlock.next = 0;
            newBlock.previous = (unsigned long)block;
            block->next = address;
        }
    }
    memcpy((void *)address, &newBlock, sizeof(Block));
    return 1;
}

/**
 * @description: Alloc a block of memory.
 * @param dpHandler DP handle.
 * @param size      Size of memory block.
 * @return Handle if success, else 0.
 */
unsigned long dPartitionAlloc(unsigned long dpHandler, unsigned long size)
{
    return dPartitionAllocFirstFit(dpHandler, size);
}

/**
 * @description: Free a block of memory.
 * @param dpHandler    DP handle.
 * @param start Start address of memory block.
 * @return 1
 */
unsigned long dPartitionFree(unsigned long dpHandler, unsigned long start)
{
    return dPartitionFreeFirstFit(dpHandler, start);
}
